-module(bmula_smtps).

-behaviour(gen_server).

-export([start_link/0, init/1]).
-export([handle_cast/2, handle_info/2, handle_call/3]).
-export([terminate/2, code_change/3]).

-export([add_handler/2]).

-record(server, {socket}).
-record(session, {socket, mode, reverse_path, forward_paths, data}).

start_link() ->
	gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
	{ok, LSock} = gen_tcp:listen(8025, [list, {active, false}, {packet, line}, {reuseaddr, true}]),
	accept(LSock),
	{ok, #server{socket = LSock}};
init({socket, Sock}) ->
	{ok, reset(#session{socket = Sock, mode = initializing})}.

handle_call(_Request, _From, State) -> {reply, ignored, State}.

handle_cast({socket_control_transfered, Sock}, State) ->
	inet:setopts(Sock, [{active, true}]),
	{noreply, reply(220, "Hello", State#session{mode = command})};
handle_cast(_Request, State) -> {noreply, State}.

handle_info({tcp, _Sock, FullLine}, State = #session{mode = command}) ->
	UpperLine = string:to_upper(FullLine),
	case UpperLine of
		"QUIT" ++ _ ->
			{stop, normal, reply(221, "Goodbye", reset(State))};
		"EHLO" ++ _ ->
			{noreply, reply(250, "3spire.com You have reached an SMTP service", reset(State))};
		"HELO" ++ _ ->
			{noreply, reply(250, "3spire.com You have reached an SMTP service", reset(State))};
		"MAIL FROM:" ++ _ ->
			%M A I L   F R O M :
			[_,_,_,_,_,_,_,_,_,_ | PathAndParams] = FullLine,
			{match, [Path]} = re:run(PathAndParams, "<[^>]*>", [{capture, all, list}]),
			{noreply, reply(250, "OK", State#session{reverse_path = Path})};
		"RCPT TO:" ++ _ ->
			%R C P T   T O :
			[_,_,_,_,_,_,_,_ | PathAndParams] = FullLine,
			{match, [Path]} = re:run(PathAndParams, "<[^>]*>", [{capture, all, list}]),
			{noreply, reply(250, "OK", State#session{forward_paths = [Path | State#session.forward_paths]})};
		"DATA" ++ _ when State#session.forward_paths == [] ->
			{noreply, reply(503, "RCPT first", State)};
		"DATA" ++ _ ->
			{noreply, reply(354, "Go ahead", State#session{mode = data})};
		"RSET" ++ _ ->
			{noreply, reply(250, "OK", reset(State))};
		"VRFY" ++ _ ->
			{noreply, reply(252, "Will not VRFY", State)};
		"EXPN" ++ _ ->
			{noreply, reply(252, "Will not EXPN", State)};
		"HELP" ++ _ ->
			{noreply, reply(502, "Unimplemented", State)};
		"NOOP" ++ _ ->
			{noreply, reply(250, "OK", State)};
		_ ->
			{noreply, reply(500, "Unsupported command", State)}
	end;
handle_info({tcp, _Sock, FullLine}, State = #session{mode = data}) ->
	case FullLine of
		".\r\n" ->
			gen_event:notify(bmula_smtps_event_manager, {deliver, State#session.reverse_path, State#session.forward_paths, lists:reverse(State#session.data)}),
			{noreply, reply(250, "OK", reset(State#session{mode = command}))};
		"." ++ Line ->
			{noreply, State#session{data = [Line | State#session.data]}};
		Line ->
			{noreply, State#session{data = [Line | State#session.data]}}
	end;
handle_info({tcp_closed, _Sock}, State) ->
	{stop, normal, State};
handle_info({tcp_error, _Sock, Reason}, State) ->
	error_logger:warning_msg("SMTP session closed with socket error ~p", [Reason]),
	{stop, normal, State};
handle_info(_Message, State) -> {noreply, State}.

code_change(_OldVsn, State, _Extra) -> State.
terminate(_Reason, #server{socket = LSock}) ->
	gen_tcp:close(LSock),
	ok.

%% module
add_handler(Handler, Args) ->
	gen_event:add_handler(bmula_smtps_event_manager, Handler, Args).

%% server
accept(ListenSocket) ->
	spawn_link(fun() ->
		case gen_tcp:accept(ListenSocket) of
			{ok, Sock} ->
				accept(ListenSocket),
				{ok, Pid} = gen_server:start(?MODULE, {socket, Sock}, []),
				gen_tcp:controlling_process(Sock, Pid),
				gen_server:cast(Pid, {socket_control_transferred, Sock});
			{error, Reason} ->
				exit({error, Reason})
		end
	end).

%% session
reset(State) ->
	State#session{reverse_path = undefined, forward_paths = [], data = []}.

reply(Code, Data, State = #session{socket = Sock}) ->
	gen_tcp:send(Sock, [integer_to_list(Code), " ", Data, "\r\n"]),
	State.

